using System;
using System.Collections.Generic;
using System.Threading;

namespace SimpleSocket.Packets
{
    internal sealed class PacketQueue
    {
        public const int Idle = 1; //空闲状态
        private const int Sending = 2; //发送中
        private const int Enqueue = 3; //入列状态
        private const int Dequeue = 4; //出列状态
        public const int Closed = 5; //已关闭

        private int _state = Idle; //当前状态
        private Queue<Packet> _queue = new Queue<Packet>();
        private Action<Packet> _sendAction = null;

        public PacketQueue(Action<Packet> sendAction)
        {
            _sendAction = sendAction ?? throw new ArgumentNullException(nameof(sendAction));
        }

        public bool TrySend(Packet packet)
        {
            var spin = true;
            while (spin)
            {
                switch (this._state)
                {
                    case Idle:
                        if (Interlocked.CompareExchange(ref this._state, Sending, Idle) == Idle)
                            spin = false;
                        break;
                    case Sending:
                        if (Interlocked.CompareExchange(ref this._state, Enqueue, Sending) == Sending)
                        {
                            this._queue.Enqueue(packet);
                            this._state = Sending;
                            return true;
                        }

                        break;
                    case Enqueue:
                    case Dequeue:
                        Thread.Yield();
                        break;
                    case Closed:
                        return false;
                }
            }

            this._sendAction(packet);
            return true;
        }

        public PacketCloseResult Close()
        {
            var spin = true;
            var beforeState = -1;
            while (spin)
            {
                switch (this._state)
                {
                    case Idle:
                        if (Interlocked.CompareExchange(ref this._state, Closed, Idle) == Idle)
                        {
                            spin = false;
                            beforeState = Idle;
                        }

                        break;
                    case Sending:
                        if (Interlocked.CompareExchange(ref this._state, Closed, Sending) == Sending)
                        {
                            spin = false;
                            beforeState = Sending;
                        }

                        break;
                    case Enqueue:
                    case Dequeue:
                        Thread.Yield();
                        break;
                    case Closed:
                        return new PacketCloseResult(Closed, null);
                }
            }

            var arrPackets = this._queue.ToArray();
            this._queue.Clear();
            this._queue = null;
            this._sendAction = null;
            return new PacketCloseResult(beforeState, arrPackets);
        }

        public bool TrySendNext()
        {
            var spin = true;
            Packet packet = null;
            while (spin)
            {
                switch (this._state)
                {
                    case Sending:
                        if (Interlocked.CompareExchange(ref this._state, Dequeue, Sending) == Sending)
                        {
                            if (this._queue.Count == 0)
                            {
                                this._state = Idle;
                                return true;
                            }

                            packet = this._queue.Dequeue();
                            this._state = Sending;
                            spin = false;
                        }

                        break;
                    case Enqueue:
                        Thread.Yield();
                        break;
                    case Closed:
                        return false;
                }
            }

            this._sendAction(packet);
            return true;
        }

    }
}